//
//  LGFCrash.swift
//  EWEMdt3
//
//  Created by 来 on 2020/5/21.
//  Copyright © 2020 laiguofeng. All rights reserved.
//

import Foundation

public protocol LGFCrashDelegate: NSObjectProtocol {
    func lgf_CrashDidCatchCrash(with model: LGFCrashModel)
}

class WeakLGFCrashDelegate: NSObject {
    weak var delegate: LGFCrashDelegate?
    
    init(delegate: LGFCrashDelegate) {
        super.init()
        self.delegate = delegate
    }
}

public enum LGFCrashModelType: Int {
    case signal = 1
    case exception = 2
}

open class LGFCrashModel: NSObject {
    
    open var type: LGFCrashModelType!
    open var name: String!
    open var reason: String!
    open var appinfo: String!
    open var callStack: String!
    
    init(type: LGFCrashModelType,
         name: String,
         reason: String,
         appinfo: String,
         callStack: String) {
        super.init()
        self.type = type
        self.name = name
        self.reason = reason
        self.appinfo = appinfo
        self.callStack = callStack
    }
}

private var app_old_exceptionHandler:(@convention(c) (NSException) -> Swift.Void)? = nil

public class LGFCrash: NSObject {
    
    //--------------------------------------------------------------------------
    // MARK: 公共属性
    //--------------------------------------------------------------------------
    public private(set) static var isOpen: Bool = false
    
    //--------------------------------------------------------------------------
    // MARK: 公共方法
    //--------------------------------------------------------------------------
    open class func add(delegate: LGFCrashDelegate) {
        // 删除 null&弱 delegate
        self.delegates = self.delegates.filter {
            return $0.delegate != nil
        }
        
        // 判断是否包含参数的委托
        let contains = self.delegates.contains {
            return $0.delegate?.hash == delegate.hash
        }
        // 如果不包含，则用软包装将其附加
        if contains == false {
            let week = WeakLGFCrashDelegate(delegate: delegate)
            self.delegates.append(week)
        }
        
        if self.delegates.count > 0 {
            self.open()
        }
    }
    
    open class func remove(delegate: LGFCrashDelegate) {
        self.delegates = self.delegates.filter {
            // 过滤 null&弱 delegate
            return $0.delegate != nil
            }.filter {
                // 从参数中过滤委托
                return $0.delegate?.hash != delegate.hash
        }
        
        if self.delegates.count == 0 {
            self.close()
        }
    }
    
    // MARK: 私有方法
    private class func open() {
        guard self.isOpen == false else {
            return
        }
        LGFCrash.isOpen = true
        
        app_old_exceptionHandler = NSGetUncaughtExceptionHandler()
        NSSetUncaughtExceptionHandler(LGFCrash.RecieveException)
        self.setCrashSignalHandler()
    }
    
    private class func close() {
        guard self.isOpen == true else {
            return
        }
        LGFCrash.isOpen = false
        NSSetUncaughtExceptionHandler(app_old_exceptionHandler)
    }
    
    private class func setCrashSignalHandler(){
        signal(SIGABRT, LGFCrash.RecieveSignal)
        signal(SIGILL, LGFCrash.RecieveSignal)
        signal(SIGSEGV, LGFCrash.RecieveSignal)
        signal(SIGFPE, LGFCrash.RecieveSignal)
        signal(SIGBUS, LGFCrash.RecieveSignal)
        signal(SIGPIPE, LGFCrash.RecieveSignal)
        signal(SIGTRAP, LGFCrash.RecieveSignal)
    }
    
    private static let RecieveException: @convention(c) (NSException) -> Swift.Void = {
        (exteption) -> Void in
        if (app_old_exceptionHandler != nil) {
            app_old_exceptionHandler!(exteption);
        }
        
        guard LGFCrash.isOpen == true else {
            return
        }
        
        let callStack = exteption.callStackSymbols.joined(separator: "\r")
        let reason = exteption.reason ?? ""
        let name = exteption.name
        let appinfo = LGFCrash.appInfo()
        
        let model = LGFCrashModel(type: LGFCrashModelType.exception,
                               name:name.rawValue,
                               reason:reason,
                               appinfo:appinfo,
                               callStack:callStack)
        for delegate in LGFCrash.delegates {
            delegate.delegate?.lgf_CrashDidCatchCrash(with: model)
        }
    }
    
    private static let RecieveSignal : @convention(c) (Int32) -> Void = {
        (signal) -> Void in
        
        guard LGFCrash.isOpen == true else {
            return
        }
        
        var stack = Thread.callStackSymbols
        stack.removeFirst(2)
        let callStack = stack.joined(separator: "\r")
        let reason = "Signal \(LGFCrash.name(of: signal))(\(signal)) was raised."
        let appinfo = LGFCrash.appInfo()
        
        let model = LGFCrashModel(type: LGFCrashModelType.signal,
                               name:LGFCrash.name(of: signal),
                               reason:reason,
                               appinfo:appinfo,
                               callStack:callStack)
        
        for delegate in LGFCrash.delegates {
            delegate.delegate?.lgf_CrashDidCatchCrash(with: model)
        }
        
        LGFCrash.killApp()
    }
    
    private class func appInfo() -> String {
        let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") ?? ""
        let shortVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") ?? ""
        let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") ?? ""
        let deviceModel = UIDevice.current.model
        let systemName = UIDevice.current.systemName
        let systemVersion = UIDevice.current.systemVersion
        return "APP版本信息:\(displayName) \(shortVersion)(\(version))\n" + "设备类型:\(deviceModel)\n" + "手机系统版本:\(systemName) \(systemVersion)"
    }
    
    // 崩溃类型
    private class func name(of signal:Int32) -> String {
        switch (signal) {
        case SIGABRT:// 程序中止命令中止信号
            return "SIGABRT"
        case SIGILL:// 程序非法指令信号
            return "SIGILL"
        case SIGSEGV:// 程序无效内存中止信号
            return "SIGSEGV"
        case SIGFPE:// 程序浮点异常信号
            return "SIGFPE"
        case SIGBUS:// 程序内存字节未对齐中止信号
            return "SIGBUS"
        case SIGPIPE:// 程序Socket发送失败中止信号
            return "SIGPIPE"
        default:
            return "OTHER"
        }
    }
    
    // 崩溃监控
    private class func killApp(){
        NSSetUncaughtExceptionHandler(nil)
        signal(SIGABRT, SIG_DFL)
        signal(SIGILL, SIG_DFL)
        signal(SIGSEGV, SIG_DFL)
        signal(SIGFPE, SIG_DFL)
        signal(SIGBUS, SIG_DFL)
        signal(SIGPIPE, SIG_DFL)
        kill(getpid(), SIGKILL)
    }
    
    fileprivate static var delegates = [WeakLGFCrashDelegate]()
}


